package liquidfs.data.ops;

import java.io.*;

import com.thoughtworks.xstream.annotations.*;

import utility.*;

import liquidfs.*;
import liquidfs.core.*;
import liquidfs.data.*;
import liquidfs.data.ex.*;

public class WriteFileOperation extends AbstractFileModificationOperation {
   
   @XStreamSchemaMinimum("0")
   private long offset;
   
   @XStreamSchemaDefault("-1")
   private long fileLength = -1;
   
   private enum WriteFileOperationState {
      WRITING,
      RELAYING
   }
   
	public WriteFileOperation(Revision current, Revision previous, String filepath, long offset, int length) {
		super(current, previous, filepath);
		this.offset = offset;
		this.setMaximumInputSize(length);
	}
	
	public void setFileLength (long length) {
	   this.fileLength = length;
	}
	
	public long getFileLength (long length) {
	   return this.fileLength;
	}
	
	@Override
	public void fix (Application app, Object state, byte[] data) {
	   
	   WriteFileOperationState wfoState = (WriteFileOperationState) state;
	   
	   // Just do the whole thing again again, regardless of the state.
	   // This doesn't really need to be efficient, as it only happens on start-up, and one operation wont take long.
	   this.execute(app, new ByteBufferInputStream(data), null);
	}

	@Override
	public Void execute(Application app, InputStream in, OutputStream out) {
	   
      // Create a buffer for the incoming data
      byte[] buffer = new byte[this.getMaximumInputSize()];
      
      // Get the data that we're going to write
      try {
         in.read(buffer, 0, this.getMaximumInputSize());
      }
      catch (IOException e) {
         throw new FileSystemException("Unable to read file data from input stream.");
      }
	   
	   // Get a reference to the file system
		LiquidFileSystem fs = app.getFileSystem();
		
		// If the file is local, write it
		if (fs.isLocal(this.getFilePath())) {
		   
		   // Update the journal
	      app.getJournal().startEntry(this, WriteFileOperationState.WRITING, buffer);
		   
   		File file = fs.getLocalFile(this.getFilePath());   		
   		RandomAccessFile disk;
   		
   		try {
   		   disk = new RandomAccessFile(file, "rws");
   		}
   		catch (java.io.FileNotFoundException e) {
   		   throw new liquidfs.data.ex.FileNotFoundException("Unable to find the file " + this.getFilePath() + ": " + e.getLocalizedMessage());
   		}
   		
   		try {   		   
   		   // Write this to the file
   		   disk.seek(offset);
   		   disk.write(buffer, 0, this.getMaximumInputSize());
   		   
   		   // Override the file length, if necessary
            if (this.fileLength != -1)
               disk.setLength(this.fileLength);
   		}
   		catch (IOException e) {
   		   throw new FileSystemException("Unable to write the file " + this.getFilePath() + ": " + e.getLocalizedMessage());
   		}
   		finally {
   		   try {
   		      disk.close();
   		   }
   		   catch (IOException e) {
   		      throw new FileSystemException("Unable to properly close file " + this.getFilePath() + "; data may not have been written.");
   		   }
   		}
   		
   		// Update the file's metadata
   		Metadata metadata = fs.getMetadata(this.getFilePath());
   		metadata.setModificationDate(this.getModificationDate());
   		metadata.setLastAccessedDate(this.getModificationDate());
   		metadata.setRevision(this.getCurrentRevision());
   		fs.setMetadata(this.getFilePath(), metadata);
		}
		
		// Iterate through each device we're connected to, except the ones that have already been queried
      for (Link link : app.getConfiguration().getLinks()) {
         
         // Skip devices that have already been reached
         if (this.reached(link.getDevice())) {
            continue;
         }
         
         // Make a stream out of the buffer
         ByteBufferInputStream binIn = new ByteBufferInputStream(buffer, this.getMaximumInputSize());
         
         // Pass this message along to other links in the network
         Result<Void> result = fs.relay(link, this, binIn, null);
         
         try {
            // There's no data returned, but we call this method to check for exceptions
            result.getData();
            continue;
         }
         catch (IgnoredException e) {
            continue;   // That's fine, keep going.
         }
         catch (ConflictException e) {
            app.getProblemBox().addProblem(this, e);
            continue;
         }
         catch (FileSystemException e) {
            app.getProblemBox().addProblem(this, e);
            continue;
         }
      }
      
      // Update the journal
      app.getJournal().endEntry();
      
      return null;
	}
}
